package org.bdware.irs.backend.servicesImpl;

import org.bdware.irs.IrsBootWithTcp;
import org.bdware.irs.backend.dto.Organization;
import org.bdware.irs.backend.dto.Stat;
import org.bdware.irs.backend.utils.DOIParser;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import org.apache.log4j.Logger;
import org.bdware.irp.stateinfo.DoStateInfo;
import org.bdware.irp.stateinfo.DoipServiceStateInfo;
import org.bdware.irp.stateinfo.StateInfoBase;
import org.bdware.irp.stateinfo.UserStateInfo;
import org.bdware.irs.server.http.IrsMain;
import org.rocksdb.RocksDBException;

import java.io.File;
import java.util.*;


//由于rocksdb不支持复杂的查询，需要存储一些状态信息，包括标识所在repo，以及所有者这些信息来手动建立索引
public class BDIrsStorageImpl extends StateInfoStorageRocksDBImpl {


    static Logger logger = Logger.getLogger(BDIrsStorageImpl.class);


    //创建多个索引，USER_STATS用来存储owner的标识列表
    static final byte[] USER_STATS = "UserStats".getBytes();
    //REPO_STATS用来存储某个repo的标识列表
    static final byte[] REPO_STATS = "RepoStats".getBytes();
    //REG_STATS用来存储整个regi的标识列表
    static final byte[] REG_STATS = "RegStats".getBytes();

    //IRS_STATS用来存储整个irs的标识列表
    static final byte[] IRS_STATS = "IrsStats".getBytes();

    static String IRS_ADMIN = IrsBootWithTcp.irsConfig.prefix + "/dou.ADMIN";

    //IRS管理的下一级以及后续的前缀列表
    static final byte[] IRS_CHILDREN = "ALL_ORG".getBytes();


    //TODO 这里并发处理是否会有问题
    HashMap<String, Set<String>> userStats;
    HashMap<String, Set<String>> repoStats;
    HashMap<String, Set<String>> regStats;
    Set<String> irsStats;

    HashMap<String, Set<String>> childPrefixs;


    void loadStatus() throws RocksDBException {
        //初始化用户视图
        byte[] userStatsBytes = db.get(USER_STATS);
        if (userStatsBytes == null) {
            userStats = new HashMap<>();
            userStats.put(IRS_ADMIN, new HashSet<>());
        } else {
            userStats = new Gson().fromJson(new String(userStatsBytes), new TypeToken<HashMap<String, Set<String>>>() {
            }.getType());
        }
        //初始化repo视图
        byte[] repoStatsBytes = db.get(REPO_STATS);
        if (repoStatsBytes == null) {
            repoStats = new HashMap<>();
        } else {
            repoStats = new Gson().fromJson(new String(repoStatsBytes), new TypeToken<HashMap<String, Set<String>>>() {
            }.getType());
        }
        //初始化regi视图,这里存的每个对象看需要保存什么，标识+repo+owner？
        byte[] regStatsBytes = db.get(REG_STATS);
        if (regStatsBytes == null) {
            regStats = new HashMap<>();
        } else {
            regStats = new Gson().fromJson(new String(regStatsBytes), new TypeToken<HashMap<String, Set<String>>>() {
            }.getType());
        }
        //初始化rirs视图,这里存的每个对象看需要保存什么，标识+repo+owner？
        byte[] irsStatsBytes = db.get(IRS_STATS);
        if (irsStatsBytes == null) {
            irsStats = new HashSet<String>();
        } else {
            irsStats = new Gson().fromJson(new String(irsStatsBytes), new TypeToken<Set<String>>() {
            }.getType());
        }
    }

    void saveStatus() throws RocksDBException {
        byte[] userStatsBytes = new Gson().toJson(userStats).getBytes();
        byte[] repoStatsBytes = new Gson().toJson(repoStats).getBytes();
        byte[] regStatsBytes = new Gson().toJson(regStats).getBytes();
        byte[] irsStatsBytes = new Gson().toJson(irsStats).getBytes();
        db.put(USER_STATS, userStatsBytes);
        db.put(REPO_STATS, repoStatsBytes);
        db.put(REG_STATS, regStatsBytes);
        db.put(IRS_STATS, irsStatsBytes);
    }

    public BDIrsStorageImpl(String dbPath) {
        super(dbPath);
        logger.info("[Init irs storage with actual rocksdb path:] " + new File(dbPath).getAbsolutePath());
        //初始化状态
        try {
            loadStatus();
        } catch (RocksDBException e) {
            e.printStackTrace();
        }
    }

    public BDIrsStorageImpl() {
        this("./rocksdb_irs/");
    }

    //op为0标识删除，1标识创建
    void updateStatus(StateInfoBase stateInfo, int op) {

        try {
            loadStatus();
        } catch (RocksDBException e) {
            e.printStackTrace();
        }

        if (stateInfo == null)
            return;
        //TODO 更新
        String doi = stateInfo.getIdentifier();
        String type = DOIParser.getType(doi);

        switch (type) {
            case "dou":
                //用户状态信息
                UserStateInfo userStateInfo = UserStateInfo.fromStateInfoBase(stateInfo);
                if (op == 0) {

                } else {

                }
                break;
            case "doip":
                //doip状态信息（主要是指regi和repo）
                DoipServiceStateInfo doipServiceStateInfo = DoipServiceStateInfo.fromStateInfoBase(stateInfo);
                String owner = doipServiceStateInfo.getOwner();
                String doipType = doipServiceStateInfo.getType();
                logger.info("doip: " + doipType);
                if ("registry".equals(doipType) || "repository".equals(doipType)) {
                    if (op == 0) {
                        if (userStats.get(IRS_ADMIN) != null) {
                            userStats.get(IRS_ADMIN).remove(doi);
                        }

                    } else {

                        if (userStats.get(IRS_ADMIN) == null) {
                            userStats.put(IRS_ADMIN, new HashSet<>());
                        }
                        userStats.get(IRS_ADMIN).add(doi);
                    }
                }
                break;
            case "do":

                //只有普通的do才会在irsStats中创建索引
                if (op == 0) {
                    //删除irs索引
                    irsStats.remove(doi);
                } else {
                    irsStats.add(doi);
                }

                //普通do状态信息
                DoStateInfo doStateInfo = DoStateInfo.fromStateInfoBase(stateInfo);

                String doOwner = doStateInfo.getOwner();
                String doRepo = doStateInfo.getRepository();
                if (op == 0) {
                    //删除 用户 索引
                    if (userStats.get(doOwner) != null) {
                        userStats.get(doOwner).remove(doi);
                    }

                    //删除repo索引
                    if (repoStats.get(doRepo) != null) {
                        repoStats.get(doRepo).remove(doi);
                    }
                } else {
                    //增加用户索引
                    if (userStats.get(doOwner) == null) {
                        userStats.put(doOwner, new HashSet<>());
                    }
                    userStats.get(doOwner).add(doi);

                    //增加repo索引
                    if (repoStats.get(doRepo) == null) {
                        repoStats.put(doOwner, new HashSet<>());
                    }
                    repoStats.get(doOwner).add(doi);
                }
        }

        //保存状态
        try {
            saveStatus();
        } catch (RocksDBException e) {
            e.printStackTrace();
        }


    }

    @Override
    public StateInfoBase getStateInfoByID(String id) {
        return super.getStateInfoByID(id);
    }

    @Override
    public boolean saveStateInfo(StateInfoBase stateInfo) {
        logger.info("===save stateInfo====");
        logger.info(new Gson().toJson(stateInfo));
        boolean result = super.saveStateInfo(stateInfo);
        if (result) {
            updateStatus(stateInfo, 1);
        }
        return result;
    }

    @Override
    public String createStateInfo(StateInfoBase stateInfo, String type) {
        logger.info("===create stateInfo====");
        logger.info(new Gson().toJson(stateInfo));
        String result = super.createStateInfo(stateInfo, type);
        stateInfo.setIdentifier(result);
        updateStatus(stateInfo, 1);
        return result;
    }

    @Override
    public boolean deleteStateInfoByID(String id) {
        StateInfoBase stateInfoBase = getStateInfoByID(id);
        boolean result = super.deleteStateInfoByID(id);
        updateStatus(stateInfoBase, 0);
        return result;
    }

    @Override
    public StateInfoBase updateStateInfo(StateInfoBase stateInfo) {
        logger.info("===更新doi信息： " + stateInfo.getIdentifier());
        StateInfoBase result = super.updateStateInfo(stateInfo);
        updateStatus(stateInfo, 1);
        return result;
    }


    public Set<String> getAllUsers() {
        try {
            loadStatus();
        } catch (RocksDBException e) {
            e.printStackTrace();
        }
        return userStats.keySet();
    }

    public Set<String> getStateInfosByUserID(String userID) {
        try {
            loadStatus();
        } catch (RocksDBException e) {
            e.printStackTrace();
        }

        return userStats.get(userID);
    }

    public Set<String> getStateInfosByRepoID(String repoID) {
        try {
            loadStatus();
        } catch (RocksDBException e) {
            e.printStackTrace();
        }
        return repoStats.get(repoID);
    }

    public Set<String> getStateInfosByRegID(String regID) {
        try {
            loadStatus();
        } catch (RocksDBException e) {
            e.printStackTrace();
        }
        return regStats.get(regID);
    }

    public Set getRepos() {
        try {
            loadStatus();
        } catch (RocksDBException e) {
            e.printStackTrace();
        }
        Set<String> repoIDs = repoStats.keySet();
        return repoIDs;
    }

    public Set getRegs() {
        try {
            loadStatus();
        } catch (RocksDBException e) {
            e.printStackTrace();
        }
        Set<String> regIDs = regStats.keySet();
        return regIDs;
    }


    public List getStateInfos(Collection<String> ids) {
        List results = new ArrayList();
        for (String repo : ids) {
            StateInfoBase stateInfo = super.getStateInfoByID(repo);
            results.add(new Gson().toJson(stateInfo));
        }
        return results;
    }

    public List getBindedRepoAndRegs() {
        try {
            loadStatus();
        } catch (RocksDBException e) {
            e.printStackTrace();
        }
        Set<String> repos = userStats.get(IRS_ADMIN);
        return getStateInfos(repos);
    }

    public List getAllStateInfos() {
        try {
            loadStatus();
        } catch (RocksDBException e) {
            e.printStackTrace();
        }
        return getStateInfos(irsStats);
    }

    //===========前缀管理相关的内容============

    //op为0表示删除，1表示添加
    public void updateOrganizations(String doi, int op) {
        Set<String> organizations;
        try {
            byte[] orgListBytes = db.get(IRS_CHILDREN);
            if (orgListBytes == null)
                organizations = new HashSet<>();
            else {
                String orgListStr = new String(orgListBytes);
                organizations = new Gson().fromJson(orgListStr, new com.google.common.reflect.TypeToken<HashSet<String>>() {
                }.getType());
            }
            if (op == 1) {
                organizations.add(doi);
            } else {
                organizations.remove(doi);
            }
            db.put(IRS_CHILDREN, new Gson().toJson(organizations).getBytes());
        } catch (RocksDBException e) {
            e.printStackTrace();
        }
    }


    public boolean saveOrganization(Organization organization) {
        try {
            db.put(organization.getDoi().getBytes(), new Gson().toJson(organization).getBytes());
            updateOrganizations(organization.getDoi(), 1);
            return true;
        } catch (RocksDBException e) {
            e.printStackTrace();
            return false;
        }
    }

    public boolean updateOrganization(Organization organization) {
        try {
            db.put(organization.getDoi().getBytes(), new Gson().toJson(organization).getBytes());
            return true;
        } catch (RocksDBException e) {
            e.printStackTrace();
            return false;
        }
    }

    public boolean deleteOrganization(Organization organization) {
        try {
            db.delete(organization.getDoi().getBytes());
            updateOrganizations(organization.getDoi(), 0);
            return true;
        } catch (RocksDBException e) {
            e.printStackTrace();
            return false;
        }
    }

    public Organization getOrganization(String doi) {
        Organization organization = null;
        try {
            byte[] orgBytes = db.get(doi.getBytes());
            String orgStr = new String(orgBytes);
            organization = new Gson().fromJson(orgStr, Organization.class);
        } catch (RocksDBException e) {
            e.printStackTrace();
        } finally {
            return organization;
        }
    }

    public List<Organization> getAllOrganization() {
        ArrayList<Organization> results = new ArrayList();
        byte[] orgListBytes = new byte[0];
        try {
            HashSet<String> organizations;
            orgListBytes = db.get(IRS_CHILDREN);
            if (orgListBytes == null) {
                organizations = new HashSet<>();
            } else {
                String orgListStr = new String(orgListBytes);
                organizations = new Gson().fromJson(orgListStr, new com.google.common.reflect.TypeToken<HashSet<String>>() {
                }.getType());
            }

            List<byte[]> ids = new ArrayList<>();
            for (String key : organizations) {
                ids.add(key.getBytes());
            }
            List<byte[]> orgDetails = db.multiGetAsList(ids);
            for (byte[] orgByte : orgDetails) {
                results.add(new Gson().fromJson(new String(orgByte), Organization.class));
            }
        } catch (RocksDBException e) {
            e.printStackTrace();
        } finally {
            return results;
        }
    }

    public Stat getStat() {
        try {
            loadStatus();
        } catch (RocksDBException e) {
            e.printStackTrace();
        }
        Stat stat = new Stat();
        stat.setOrgNum(getAllOrganization().size());
        stat.setUserNum(userStats.keySet().size());
        stat.setRepoNum(repoStats.keySet().size());
        stat.setRegNum(regStats.keySet().size());
        stat.setIrsNum(irsStats.size());
        return stat;
    }
}
